@defikitdotnet/education-module-ai
Version:
AI Education Module using Agent Framework
761 lines • 86.3 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var jsx_runtime_1 = require("react/jsx-runtime");
var react_1 = require("react");
var router_1 = require("next/router");
var useCourses_1 = require("../../../hooks/useCourses");
var immer_1 = require("immer");
var button_1 = require("../../../components/ui/button");
var input_1 = require("../../../components/ui/input");
var textarea_1 = require("../../../components/ui/textarea");
var select_1 = require("../../../components/ui/select");
var card_1 = require("../../../components/ui/card");
var accordion_1 = require("../../../components/ui/accordion");
var sonner_1 = require("sonner");
var lucide_react_1 = require("lucide-react");
var CategoryManager_1 = __importDefault(require("../../../components/CategoryManager"));
var tabs_1 = require("../../../components/ui/tabs");
var file_upload_1 = require("../../../components/ui/file-upload");
var react_markdown_1 = __importDefault(require("react-markdown"));
var label_1 = require("../../../components/ui/label");
// Add mappings for display names (similar to create.tsx)
var levelNames = {
beginner: "Beginner",
intermediate: "Intermediate",
advanced: "Advanced",
};
var visibilityNames = {
private: "Private",
public: "Public",
};
// Define lecture types for the select dropdown
var lectureTypes = [
{ value: "text", label: "Text" },
{ value: "video", label: "Video URL" },
{ value: "h5p", label: "H5P Content ID/URL" },
];
var CourseEditPage = function () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
var router = (0, router_1.useRouter)();
var courseId = typeof router.query.id === "string" ? router.query.id : null;
var _k = (0, react_1.useState)(""), teacherId = _k[0], setTeacherId = _k[1];
var _l = (0, react_1.useState)(null), course = _l[0], setCourse = _l[1];
var _m = (0, react_1.useState)(true), isLoading = _m[0], setIsLoading = _m[1];
var _o = (0, react_1.useState)(""), error = _o[0], setError = _o[1];
var _p = (0, react_1.useState)(false), isEditing = _p[0], setIsEditing = _p[1];
// --- State for editable data ---
var _q = (0, react_1.useState)(null), editableCourseData = _q[0], setEditableCourseData = _q[1];
// --- State for thumbnail tab ---
var _r = (0, react_1.useState)("url"), thumbnailTab = _r[0], setThumbnailTab = _r[1]; // Default to URL if existing
// --- State for fetched category names ---
var _s = (0, react_1.useState)(null), categoryDetails = _s[0], setCategoryDetails = _s[1];
var _t = (0, react_1.useState)(false), isLoadingCategories = _t[0], setIsLoadingCategories = _t[1];
// --- State for Quiz Preview Modal ---
var _u = (0, react_1.useState)(false), isQuizPreviewOpen = _u[0], setIsQuizPreviewOpen = _u[1];
var _v = (0, react_1.useState)(null), selectedQuizPreview = _v[0], setSelectedQuizPreview = _v[1];
// --- State for Lecture Preview Modal ---
var _w = (0, react_1.useState)(false), isLecturePreviewOpen = _w[0], setIsLecturePreviewOpen = _w[1];
var _x = (0, react_1.useState)(null), selectedLecturePreview = _x[0], setSelectedLecturePreview = _x[1];
var _y = (0, useCourses_1.useCourses)(teacherId, process.env.NEXT_PUBLIC_BACKEND_URL || "/api"), fetchCourseDetail = _y.fetchCourseDetail, updateCourse = _y.updateCourse, deleteCourse = _y.deleteCourse;
// Get teacher ID from various sources
(0, react_1.useEffect)(function () {
// First check URL query parameter
var queryTeacherId = typeof router.query.teacherId === "string" ? router.query.teacherId : null;
if (queryTeacherId) {
setTeacherId(queryTeacherId);
}
else if (typeof window !== "undefined") {
// Check localStorage for teacher info
var storedTeacher = localStorage.getItem('currentTeacher');
if (storedTeacher) {
try {
var teacherData = JSON.parse(storedTeacher);
if (teacherData && teacherData.id) {
setTeacherId(teacherData.id);
}
}
catch (e) {
console.error("Failed to parse teacher data:", e);
}
}
}
}, [router.query.teacherId]);
// --- Fetch Category Details Function ---
var fetchCategoryDetails = (0, react_1.useCallback)(function (categoryIds) { return __awaiter(void 0, void 0, void 0, function () {
var categoryPromises, fetchedCategories, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!categoryIds || categoryIds.length === 0) {
setCategoryDetails([]); // No categories to fetch
return [2 /*return*/];
}
setIsLoadingCategories(true);
_a.label = 1;
case 1:
_a.trys.push([1, 3, 4, 5]);
categoryPromises = categoryIds.map(function (id) {
return fetch("".concat(process.env.NEXT_PUBLIC_BACKEND_URL || "/api", "/api/categories/").concat(id)).then(function (res) {
if (!res.ok)
throw new Error("Failed to fetch category ".concat(id));
return res.json();
});
});
return [4 /*yield*/, Promise.all(categoryPromises)];
case 2:
fetchedCategories = _a.sent();
setCategoryDetails(fetchedCategories);
return [3 /*break*/, 5];
case 3:
err_1 = _a.sent();
console.error("Error fetching category details:", err_1);
sonner_1.toast.error("Could not load category names.");
// Set to empty array or null depending on desired error display
setCategoryDetails(null);
return [3 /*break*/, 5];
case 4:
setIsLoadingCategories(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, []); // Removed apiBaseUrl from dependency array as it uses process.env directly
// Fetch course details - Modified to not depend on teacherId
var loadCourseData = (0, react_1.useCallback)(function () { return __awaiter(void 0, void 0, void 0, function () {
var courseData, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!courseId) {
setError("Course ID is missing.");
setIsLoading(false);
return [2 /*return*/];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, 4, 5]);
setIsLoading(true);
setError("");
return [4 /*yield*/, fetchCourseDetail(courseId)];
case 2:
courseData = _a.sent();
if (!courseData) {
throw new Error("Course not found or failed to fetch.");
}
console.log("Loaded course data:", courseData);
setCourse(courseData);
// Reset category details before fetching new ones
setCategoryDetails(null);
// Fetch category names after getting course data
if ((courseData === null || courseData === void 0 ? void 0 : courseData.categories) && Array.isArray(courseData.categories)) {
fetchCategoryDetails(courseData.categories);
}
else {
setCategoryDetails([]); // No categories
}
return [3 /*break*/, 5];
case 3:
err_2 = _a.sent();
console.error("Error loading course:", err_2);
setError(err_2 instanceof Error ? err_2.message : "Failed to load course details");
setCourse(null);
return [3 /*break*/, 5];
case 4:
setIsLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); }, [courseId, fetchCourseDetail, fetchCategoryDetails]);
(0, react_1.useEffect)(function () {
if (courseId) {
loadCourseData();
}
}, [courseId, loadCourseData]);
// --- Handler to start editing ---
var handleStartEditing = function () {
var _a;
if (!course)
return;
// Helper function to map fetched quiz question to UpdateQuizQuestionDTO
var mapQuizQuestion = function (q) {
var _a;
return ({
id: q.id,
question: q.question,
options: Array.isArray(q.options) ? q.options : [],
correctAnswer: (_a = q.answer) !== null && _a !== void 0 ? _a : "", // Map 'answer' to 'correctAnswer'
explanation: q.explanation || "",
});
};
// Helper function to map fetched quiz to UpdateQuizDTO
var mapQuiz = function (quizData) {
var _a;
return ({
id: quizData.id,
title: quizData.title,
description: quizData.description || "",
questions: (_a = (quizData.questions && Array.isArray(quizData.questions)
? quizData.questions.map(mapQuizQuestion)
: [])) !== null && _a !== void 0 ? _a : [],
});
};
setEditableCourseData({
title: course.title,
description: course.description || "",
thumbnail: course.thumbnail || "",
level: course.level,
visibility: course.visibility,
categories: Array.isArray(course.categories) ? course.categories : [],
// Ensure sections are sorted and mapped correctly
sections: ((_a = course.sections) !== null && _a !== void 0 ? _a : [])
.sort(function (a, b) { return a.orderIndex - b.orderIndex; })
.map(function (section) {
var _a;
return ({
id: section.id,
title: section.title,
description: section.description || "",
orderIndex: section.orderIndex,
// Lectures mapping remains the same
lectures: ((_a = section.lectures) !== null && _a !== void 0 ? _a : [])
.sort(function (a, b) { return a.orderIndex - b.orderIndex; })
.map(function (lecture) {
var mappedType = "text";
if (["text", "video", "h5p"].includes(lecture.type)) {
mappedType = lecture.type;
}
else if (lecture.type === "presentation") {
mappedType = "h5p";
}
return {
id: lecture.id,
title: lecture.title,
content: lecture.content,
type: mappedType,
orderIndex: lecture.orderIndex,
};
}),
// Initialize quizzes array - put existing single quiz into array if it exists
// This assumes the fetched data still has `section.quiz`
quizzes: section.quiz ? [mapQuiz(section.quiz)] : [],
});
}),
});
setIsEditing(true);
setThumbnailTab(course.thumbnail && !course.thumbnail.startsWith("http") ? "upload" : "url");
};
// --- Handler to cancel editing ---
var handleCancelEditing = function () {
setEditableCourseData(null);
setIsEditing(false);
setError("");
};
// --- Refactored Handle course update ---
var handleSubmit = function (e) { return __awaiter(void 0, void 0, void 0, function () {
var payloadToSubmit, updatedCourseData, err_3, errorMessage;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
e.preventDefault();
if (!courseId || !editableCourseData)
return [2 /*return*/];
payloadToSubmit = __assign(__assign({}, editableCourseData), { sections: editableCourseData.sections.map(function (editableSection, sectionIndex) {
var _a;
// Create the section object expected by the backend DTO
var backendSection = {
id: editableSection.id,
title: editableSection.title,
description: editableSection.description,
orderIndex: sectionIndex, // Ensure order index is updated
lectures: editableSection.lectures.map(function (lecture, lectureIndex) { return (__assign(__assign({}, lecture), { orderIndex: lectureIndex })); }),
// Take the first quiz from the array, or null if empty
quiz: editableSection.quizzes && editableSection.quizzes.length > 0
? __assign(__assign({}, editableSection.quizzes[0]), {
// Ensure questions within the first quiz are properly formatted if needed
questions: ((_a = editableSection.quizzes[0].questions) !== null && _a !== void 0 ? _a : []).map(function (q) { return (__assign({}, q)); }) // Simple map for now
}) : null,
};
return backendSection;
}) });
console.log("Submitting update payload (mapped back to single quiz):", payloadToSubmit);
_a.label = 1;
case 1:
_a.trys.push([1, 3, 4, 5]);
setIsLoading(true);
setError("");
return [4 /*yield*/, updateCourse(courseId, payloadToSubmit)];
case 2:
updatedCourseData = _a.sent();
if (!updatedCourseData) {
throw new Error("Update failed: Course data not returned.");
}
// Reset state with newly fetched data structure
setCourse(updatedCourseData);
if (updatedCourseData.categories && Array.isArray(updatedCourseData.categories)) {
fetchCategoryDetails(updatedCourseData.categories);
}
else {
setCategoryDetails([]);
}
setEditableCourseData(null); // Clear editing state
setIsEditing(false);
sonner_1.toast.success("Course updated successfully!");
return [3 /*break*/, 5];
case 3:
err_3 = _a.sent();
console.error("Error updating course:", err_3);
errorMessage = err_3 instanceof Error ? err_3.message : "Failed to update course";
setError(errorMessage);
sonner_1.toast.error("Error updating course: ".concat(errorMessage));
return [3 /*break*/, 5];
case 4:
setIsLoading(false);
return [7 /*endfinally*/];
case 5: return [2 /*return*/];
}
});
}); };
var handleDelete = function () { return __awaiter(void 0, void 0, void 0, function () {
var err_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!courseId ||
!confirm("Are you sure you want to delete this course? This cannot be undone."))
return [2 /*return*/];
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
setIsLoading(true);
return [4 /*yield*/, deleteCourse(courseId)];
case 2:
_a.sent();
sonner_1.toast.success("Course deleted successfully!");
router.push("/teacher?teacherId=".concat(teacherId));
return [3 /*break*/, 4];
case 3:
err_4 = _a.sent();
setError(err_4 instanceof Error ? err_4.message : "Failed to delete course");
sonner_1.toast.error("Error deleting course: ".concat(err_4 instanceof Error ? err_4.message : "Unknown error"));
setIsLoading(false);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
}); };
// --- Handler for basic course field changes ---
var handleFieldChange = function (field, value) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft && field in draft) {
// Type assertion needed because 'field' is a key, and TS struggles with indexed access assignment
draft[field] = value;
}
}));
};
// --- Handlers for Section changes ---
var handleSectionChange = function (sectionIndex, field, // Use EditableSection type
value) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) {
draft.sections[sectionIndex][field] = value;
}
}));
};
var handleAddSection = function () {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft) {
var newOrderIndex = draft.sections.length;
draft.sections.push({
// No 'id' for new sections
title: "New Section ".concat(newOrderIndex + 1),
description: "",
orderIndex: newOrderIndex, // Set initial order index
lectures: [],
quizzes: [],
});
}
}));
};
var handleDeleteSection = function (sectionIndex) {
if (!confirm("Are you sure you want to delete this section and all its content?"))
return;
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft === null || draft === void 0 ? void 0 : draft.sections) {
draft.sections.splice(sectionIndex, 1);
// Re-calculate orderIndex after deletion is not strictly necessary here
// as the submit handler will fix it, but could be done for consistency:
// draft.sections.forEach((sec, idx) => sec.orderIndex = idx);
}
}));
};
var handleMoveSection = function (sectionIndex, direction) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
if (!(draft === null || draft === void 0 ? void 0 : draft.sections))
return;
var sections = draft.sections;
var targetIndex = direction === "up" ? sectionIndex - 1 : sectionIndex + 1;
if (targetIndex >= 0 && targetIndex < sections.length) {
// Swap elements
_a = [
sections[targetIndex],
sections[sectionIndex],
], sections[sectionIndex] = _a[0], sections[targetIndex] = _a[1];
// Update orderIndex immediately (optional but good practice)
// sections[sectionIndex].orderIndex = sectionIndex;
// sections[targetIndex].orderIndex = targetIndex;
// The submit handler will set final correct order anyway
}
}));
};
// --- Handlers for Lecture changes ---
var handleLectureChange = function (sectionIndex, lectureIndex, field, // Exclude fields not directly edited here
value // Handle type change specifically
) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
if ((_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.lectures[lectureIndex]) {
draft.sections[sectionIndex].lectures[lectureIndex][field] = value;
}
}));
};
var handleAddLecture = function (sectionIndex) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) {
var section = draft.sections[sectionIndex];
var newOrderIndex = section.lectures.length;
section.lectures.push({
// No 'id' for new lectures
title: "New Lecture ".concat(newOrderIndex + 1),
content: "",
type: "text", // Default type
orderIndex: newOrderIndex, // Set initial order index
});
}
}));
};
var handleDeleteLecture = function (sectionIndex, lectureIndex) {
if (!confirm("Are you sure you want to delete this lecture?"))
return;
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
if ((_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.lectures) {
draft.sections[sectionIndex].lectures.splice(lectureIndex, 1);
// Re-calculate orderIndex after deletion (optional here)
// draft.sections[sectionIndex].lectures.forEach((lec, idx) => lec.orderIndex = idx);
}
}));
};
var handleMoveLecture = function (sectionIndex, lectureIndex, direction) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
var _b;
var lectures = (_b = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _b === void 0 ? void 0 : _b.lectures;
if (!lectures)
return;
var targetIndex = direction === "up" ? lectureIndex - 1 : lectureIndex + 1;
if (targetIndex >= 0 && targetIndex < lectures.length) {
_a = [
lectures[targetIndex],
lectures[lectureIndex],
], lectures[lectureIndex] = _a[0], lectures[targetIndex] = _a[1];
// Update orderIndex (optional here)
// lectures[lectureIndex].orderIndex = lectureIndex;
// lectures[targetIndex].orderIndex = targetIndex;
}
}));
};
// --- Updated Handlers for Quiz changes (using quizzes array) ---
var handleQuizChange = function (sectionIndex, quizIndex, // Add quizIndex
field, value) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
// Target the correct quiz in the array
var quiz = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex];
if (quiz) {
quiz[field] = value;
}
}));
};
// Updated to push to quizzes array
var handleAddQuiz = function (sectionIndex) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) {
// Ensure quizzes array exists
if (!Array.isArray(draft.sections[sectionIndex].quizzes)) {
draft.sections[sectionIndex].quizzes = [];
}
var newQuizIndex = draft.sections[sectionIndex].quizzes.length;
// Push a new quiz object into the array
draft.sections[sectionIndex].quizzes.push({
// No 'id' for new quiz yet
title: "New Quiz ".concat(newQuizIndex + 1),
description: "",
questions: [],
});
}
}));
};
// Updated to splice from quizzes array
var handleDeleteQuiz = function (sectionIndex, quizIndex) {
if (!confirm("Are you sure you want to delete this quiz?"))
return;
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
if ((_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes) {
draft.sections[sectionIndex].quizzes.splice(quizIndex, 1); // Splice by index
}
}));
};
// --- Updated Handlers for Quiz Question changes ---
var handleQuizQuestionChange = function (sectionIndex, quizIndex, // Add quizIndex
questionIndex, field, value) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a, _b;
var question = (_b = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]) === null || _b === void 0 ? void 0 : _b.questions[questionIndex]; // Target correct quiz/question
if (question) {
question[field] = value;
if (field === "correctAnswer" && !question.options.includes(value)) {
console.warn("Correct answer doesn't match any current option.");
}
}
}));
};
// Updated to add question to the correct quiz
var handleAddQuizQuestion = function (sectionIndex, quizIndex) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a;
var quiz = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]; // Target correct quiz
if (quiz) {
// Ensure questions array exists
if (!Array.isArray(quiz.questions)) {
quiz.questions = [];
}
var questionIndex = quiz.questions.length;
quiz.questions.push({
// No id for new question
question: "New Question ".concat(questionIndex + 1),
options: ["Option 1", "Option 2"],
correctAnswer: "Option 1",
explanation: "",
});
}
}));
};
// Updated to delete question from the correct quiz
var handleDeleteQuizQuestion = function (sectionIndex, quizIndex, // Add quizIndex
questionIndex) {
if (!confirm("Are you sure you want to delete this question?"))
return;
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a, _b;
var questions = (_b = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]) === null || _b === void 0 ? void 0 : _b.questions; // Target correct quiz
if (questions) {
questions.splice(questionIndex, 1);
}
}));
};
// --- Updated Handlers for Quiz Question Option changes ---
var handleQuizOptionChange = function (sectionIndex, quizIndex, // Add quizIndex
questionIndex, optionIndex, value) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a, _b;
var question = (_b = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]) === null || _b === void 0 ? void 0 : _b.questions[questionIndex]; // Target correct quiz/question
if (question === null || question === void 0 ? void 0 : question.options) {
var oldOptionValue = question.options[optionIndex];
question.options[optionIndex] = value;
if (question.correctAnswer === oldOptionValue) {
question.correctAnswer = value;
}
}
}));
};
// Updated to add option to the correct quiz/question
var handleAddQuizOption = function (sectionIndex, quizIndex, questionIndex) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a, _b;
var question = (_b = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]) === null || _b === void 0 ? void 0 : _b.questions[questionIndex]; // Target correct quiz/question
if (question) {
// Ensure options array exists
if (!Array.isArray(question.options)) {
question.options = [];
}
question.options.push("New Option ".concat(question.options.length + 1));
}
}));
};
// Updated to delete option from the correct quiz/question
var handleDeleteQuizOption = function (sectionIndex, quizIndex, // Add quizIndex
questionIndex, optionIndex) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
var _a, _b;
var question = (_b = (_a = draft === null || draft === void 0 ? void 0 : draft.sections[sectionIndex]) === null || _a === void 0 ? void 0 : _a.quizzes[quizIndex]) === null || _b === void 0 ? void 0 : _b.questions[questionIndex]; // Target correct quiz/question
if ((question === null || question === void 0 ? void 0 : question.options) && question.options.length > 1) {
var deletedOption = question.options.splice(optionIndex, 1)[0];
if (question.correctAnswer === deletedOption && question.options.length > 0) {
question.correctAnswer = question.options[0];
}
else if (question.options.length === 0) {
question.correctAnswer = "";
}
}
else {
sonner_1.toast.info("Cannot delete the last option.");
}
}));
};
// --- Handler for category changes in edit mode ---
var handleEditableCategoriesChange = function (selectedCategoryObjects) {
setEditableCourseData((0, immer_1.produce)(function (draft) {
if (draft) {
draft.categories = selectedCategoryObjects.map(function (cat) { return cat.id; });
}
}));
};
// --- Handler for file upload ---
var handleFileUpload = function (fileUrl) {
// Assuming fileUrl is relative like "/uploads/image.jpg"
// Prepend the API base URL if necessary, or store relative path based on setup
// const fullUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}${fileUrl}`; // Example if full URL needed
handleFieldChange("thumbnail", fileUrl); // Store relative path
setThumbnailTab("upload"); // Switch tab to show preview if needed
};
// --- Loading / Error / Not Found States ---
if (isLoading && !course && !editableCourseData) {
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex justify-center items-center p-8", children: (0, jsx_runtime_1.jsx)("div", { className: "animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600" }) }));
}
if (error && !course && !isEditing) {
// Added !isEditing condition to allow showing errors during edit mode below
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen p-8", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { className: "border-red-400", children: [(0, jsx_runtime_1.jsx)(card_1.CardHeader, { children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-red-700", children: "Error Loading Course" }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-red-700", children: error }), (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", className: "mt-4", onClick: function () { return router.push("/teacher?teacherId=".concat(teacherId)); }, children: "Back to Dashboard" }), (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", className: "mt-4 ml-2", onClick: loadCourseData, children: "Retry" })] })] }) }));
}
if (!course && !isEditing) {
// Handled by the loading/error states above if fetch fails
// This state might be reached if courseId is invalid but teacherId is present
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen p-8", children: (0, jsx_runtime_1.jsxs)(card_1.Card, { className: "border-yellow-400", children: [(0, jsx_runtime_1.jsx)(card_1.CardHeader, { children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { className: "text-yellow-700", children: "Not Found" }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-yellow-700", children: "Course not found or ID is invalid." }), (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", className: "mt-4", onClick: function () { return router.push("/teacher?teacherId=".concat(teacherId)); }, children: "Back to Dashboard" })] })] }) }));
}
// --- Main Render ---
// Use editable data if editing, otherwise use the fetched course data
var displayData = isEditing ? editableCourseData : course;
// Guard against rendering before data is ready (especially displayData)
if (!displayData && isEditing) {
// This might happen briefly when starting to edit
return ((0, jsx_runtime_1.jsx)("div", { className: "min-h-screen flex justify-center items-center p-8", children: (0, jsx_runtime_1.jsx)("p", { children: "Preparing edit mode..." }) }));
}
// Ensure displayData is not null before accessing its properties
if (!displayData) {
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen flex justify-center items-center p-8", children: [(0, jsx_runtime_1.jsx)("p", { children: "Loading course data..." }), " "] }));
}
// Helper to get full thumbnail URL
var getThumbnailUrl = function (thumbnailPath) {
if (!thumbnailPath)
return "/images/placeholder.jpg";
// If it's already a full URL
if (thumbnailPath.startsWith("http://") || thumbnailPath.startsWith("https://")) {
return thumbnailPath;
}
// If it's a relative path from the API uploads
if (thumbnailPath.startsWith("/uploads")) {
return "".concat(process.env.NEXT_PUBLIC_BACKEND_URL || "").concat(thumbnailPath);
}
// Otherwise, assume it's a placeholder or needs specific handling
// For safety, return placeholder if unsure
return "/images/placeholder.jpg";
};
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen bg-gray-50 pb-16", children: [(0, jsx_runtime_1.jsx)("header", { className: "bg-white shadow-sm sticky top-0 z-10", children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center", children: [(0, jsx_runtime_1.jsxs)("h1", { className: "text-2xl font-semibold text-gray-900 truncate pr-4", children: [isEditing ? "Edit Course:" : "", " ", (_a = displayData === null || displayData === void 0 ? void 0 : displayData.title) !== null && _a !== void 0 ? _a : "Loading..."] }), (0, jsx_runtime_1.jsx)("div", { className: "flex space-x-2 flex-shrink-0", children: isEditing ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", onClick: handleCancelEditing, disabled: isLoading, children: "Cancel" }), (0, jsx_runtime_1.jsx)(button_1.Button, { onClick: handleSubmit, disabled: isLoading, children: isLoading ? "Saving..." : "Save Changes" })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { variant: "outline", onClick: function () { return router.push("/teacher?teacherId=".concat(teacherId)); }, disabled: !teacherId, children: "Dashboard" }), (0, jsx_runtime_1.jsx)(button_1.Button, { variant: "default", onClick: handleStartEditing, disabled: !course || isLoading, children: "Edit Course" })] })) })] }), isEditing && error && ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded text-sm", children: (0, jsx_runtime_1.jsxs)("p", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Error:" }), " ", error] }) }))] }) }), (0, jsx_runtime_1.jsx)("main", { className: "max-w-7xl mx-auto py-6 sm:px-6 lg:px-8", children: (0, jsx_runtime_1.jsxs)("div", { className: "md:grid md:grid-cols-3 md:gap-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "md:col-span-1", children: (0, jsx_runtime_1.jsxs)("div", { className: "px-4 sm:px-0 sticky top-[calc(theme(spacing.16)+1rem)] space-y-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-medium leading-6 text-gray-900", children: isEditing ? "Edit Information" : "Course Actions" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-gray-600", children: isEditing
? "Update course details, level, visibility, and categories."
: "View course details or manage content." })] }), isEditing && editableCourseData ? ((0, jsx_runtime_1.jsx)(card_1.Card, { children: (0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "pt-6 space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "title", children: "Title" }), (0, jsx_runtime_1.jsx)(input_1.Input, { id: "title", value: editableCourseData.title, onChange: function (e) {
return handleFieldChange("title", e.target.value);
}, placeholder: "Course Title", required: true, className: "mt-1" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "description", children: "Description" }), (0, jsx_runtime_1.jsx)(textarea_1.Textarea, { id: "description", value: editableCourseData.description, onChange: function (e) {
return handleFieldChange("description", e.target.value);
}, placeholder: "Course Description", rows: 4, className: "mt-1" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { className: "block mb-1", children: "Course Thumbnail" }), (0, jsx_runtime_1.jsxs)(tabs_1.Tabs, { value: thumbnailTab, onValueChange: function (value) {
return setThumbnailTab(value);
}, className: "w-full", children: [(0, jsx_runtime_1.jsxs)(tabs_1.TabsList, { className: "grid w-full grid-cols-2 mb-2", children: [(0, jsx_runtime_1.jsxs)(tabs_1.TabsTrigger, { value: "upload", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Upload, { className: "h-4 w-4 mr-2" }), " Upload"] }), (0, jsx_runtime_1.jsxs)(tabs_1.TabsTrigger, { value: "url", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.LinkIcon, { className: "h-4 w-4 mr-2" }), " URL"] })] }), (0, jsx_runtime_1.jsxs)(tabs_1.TabsContent, { value: "upload", children: [(0, jsx_runtime_1.jsx)(file_upload_1.FileUpload, { onFileUpload: handleFileUpload }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-gray-500 mt-1", children: "Max file size 4MB. Recommended aspect ratio 16:9." })] }), (0, jsx_runtime_1.jsx)(tabs_1.TabsContent, { value: "url", children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: "thumbnail-url", value: editableCourseData.thumbnail || "", onChange: function (e) {
handleFieldChange("thumbnail", e.target.value);
// If user types a URL, make sure the tab reflects it
if (e.target.value.startsWith('http'))
setThumbnailTab("url");
}, placeholder: "Enter image URL (e.g., https://...)" }) })] }), editableCourseData.thumbnail && ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 border rounded-md p-2 relative h-32 bg-gray-100 flex justify-center items-center", children: (0, jsx_runtime_1.jsx)("img", { src: getThumbnailUrl(editableCourseData.thumbnail), alt: "Thumbnail preview", className: "max-h-full max-w-full object-contain", onError: function (e) {
e.target.src =
"/images/placeholder.jpg";
e.target.onerror = null;
} }) }))] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "level", children: "Level" }), (0, jsx_runtime_1.jsxs)(select_1.Select, { value: editableCourseData.level, onValueChange: function (value) {
return handleFieldChange("level", value);
}, children: [(0, jsx_runtime_1.jsx)(select_1.SelectTrigger, { id: "level", className: "mt-1", children: (0, jsx_runtime_1.jsx)(select_1.SelectValue, { placeholder: "Select level" }) }), (0, jsx_runtime_1.jsx)(select_1.SelectContent, { children: Object.entries(levelNames).map(function (_a) {
var key = _a[0], name = _a[1];
return ((0, jsx_runtime_1.jsx)(select_1.SelectItem, { value: key, children: name }, key));
}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { htmlFor: "visibility", children: "Visibility" }), (0, jsx_runtime_1.jsxs)(select_1.Select, { value: editableCourseData.visibility, onValueChange: function (value) {
return handleFieldChange("visibility", value);
}, children: [(0, jsx_runtime_1.jsx)(select_1.SelectTrigger, { id: "visibility", className: "mt-1", children: (0, jsx_runtime_1.jsx)(select_1.SelectValue, { placeholder: "Select visibility" }) }), (0, jsx_runtime_1.jsx)(select_1.SelectContent, { children: Object.entries(visibilityNames).map(function (_a) {
var key = _a[0], name = _a[1];
return ((0, jsx_runtime_1.jsx)(select_1.SelectItem, { value: key, children: name }, key));
}) })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(label_1.Label, { className: "block mb-1", children: "Categories" }), (0, jsx_runtime_1.jsx)(CategoryManager_1.default, { mode: "select", teacherId: teacherId, apiBaseUrl: process.env.NEXT_PUBLIC_BACKEND_URL || "/api",
// Pass only the IDs to the manager
selectedCategories: editableCourseData.categories,
// Receive the full Category objects back if needed, or just IDs
onCategoriesChange: function (selectedIds) {
return handleFieldChange("categories", selectedIds);
} })] })] }) })) : (
/* Read Mode Actions */
(0, jsx_runtime_1.jsx)("div", { className: "mt-6 space-y-4", children: (0, jsx_runtime_1.jsxs)(button_1.Button, { variant: "destructive", className: "w-full justify-start", onClick: handleDelete, disabled: !course || isLoading, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Trash2, { className: "mr-2 h-4 w-4" }), " Delete Course"] }) }))] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-5 md:mt-0 md:col-span-2 space-y-6", children: [!isEditing && course && ((0, jsx_runtime_1.jsxs)(card_1.Card, { children: [(0, jsx_runtime_1.jsx)(card_1.CardHeader, { children: (0, jsx_runtime_1.jsx)(card_1.CardTitle, { children: "Course Details" }) }), (0, jsx_runtime_1.jsxs)(card_1.CardContent, { className: "space-y-4", children: [course.thumbnail && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { className: "text-sm font-medium text-gray-500 mb-1", children: "Thumbnail" }), (0, jsx_runtime_1.jsx)("div", { className: "border round